﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GestureLib;
using System.Net.Sockets;
using System.Net;
using System.IO;
using System.Threading;
using System.Drawing;
using System.Timers;
using System.Windows.Threading;
using System.Globalization;

namespace GestureLib.Implementation
{
    public class TcpServerGestureDevice : AbstractGestureDevice, IMouseEmulationDevice, IAccelerationGestureDevice
    {
        private delegate void PerformActionsDelegate(string deliveredData);

        private TcpListener _tcpListener;
        private TcpClient _connectedTcpClient;
        private Thread _listenTcpDataThread;

        private float _currentPositionX;
        private float _currentPositionY;

        private Size _screenSize;

        private System.Timers.Timer _motionTimer; 

        public TcpServerGestureDevice(Size screenSize)
        {
            _screenSize = screenSize;
        }

        public Dispatcher Dispatcher { get; set; }

        public int ServerPort { get; set; }

        public void StartListening()
        {
            if (_tcpListener != null)
            {
                StopListening();
            }

            _tcpListener = new TcpListener(IPAddress.Any, 12345);
            _tcpListener.Start();

            _tcpListener.BeginAcceptTcpClient(
                new AsyncCallback(AcceptTcpClientCallback), 
                _tcpListener);
        }

        public void StopListening()
        {
            if (_tcpListener != null)
            {
                _tcpListener.Stop();
                _tcpListener = null;

                if(_connectedTcpClient != null &&
                    _connectedTcpClient.Connected)
                {
                    _connectedTcpClient.Close();
                    _listenTcpDataThread.Abort();

                    _connectedTcpClient = null;
                    _listenTcpDataThread = null;
                }
            }
        }

        private void AcceptTcpClientCallback(IAsyncResult asyncResult)
        {
            TcpListener listener = (TcpListener)asyncResult.AsyncState;
            _connectedTcpClient = listener.EndAcceptTcpClient(asyncResult);

            _listenTcpDataThread = new Thread(new ThreadStart(ListenTcpData));
            _listenTcpDataThread.Start();

            _motionTimer = new System.Timers.Timer(10.0);
            _motionTimer.Elapsed += new ElapsedEventHandler(MotionTimer_Elapsed);
            _motionTimer.Start();
        }

        private void ListenTcpData()
        {
            StreamReader inputStream = new StreamReader(_connectedTcpClient.GetStream());

            while (_connectedTcpClient.Connected)
            {
                try
                {
                    string tcpDeliveredData = inputStream.ReadLine();


                    PerformActionsDelegate dlg = new PerformActionsDelegate(PerformActions);
                    
                    if (Dispatcher != null)
                    {
                        Dispatcher.BeginInvoke(DispatcherPriority.Normal, dlg, tcpDeliveredData);
                    }
                    else
                    {
                        dlg.Invoke(tcpDeliveredData);
                    }
                    
                    
                    //PerformActions(tcpDeliveredData);
                }
                catch (Exception ex)
                {
                    _connectedTcpClient.Close();
                }
            }

            if (_connectedTcpClient.Connected)
            {
                _connectedTcpClient.Close();
            }

            if (_motionTimer != null)
            {
                _motionTimer.Stop();
                _motionTimer.Close();
                _motionTimer = null;
            }
        }

        #region IMouseEmulationDevice Members

        public event EventHandler<MouseEmulationStateEventArgs> MouseEmulationStateChanged;

        #endregion

        private void PerformActions(string deliveredData)
        {
            System.Diagnostics.Trace.WriteLine(deliveredData);

            string[] parameters = deliveredData.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);

            if (parameters.Length == 3) //Parameter group handling (Acceleration values)
            {
                string nameX = string.Empty;
                string x = GetSeperatedValue(parameters[0], out nameX);
                float valueX = float.Parse(x, CultureInfo.InvariantCulture);

                string nameY = string.Empty;
                string y = GetSeperatedValue(parameters[1], out nameY);
                float valueY = float.Parse(y, CultureInfo.InvariantCulture);

                string nameZ = string.Empty;
                string z = GetSeperatedValue(parameters[2], out nameZ);
                float valueZ = float.Parse(z, CultureInfo.InvariantCulture);

                valueY *= -1;

                AccelerationGestureState = new AccelerationGestureState(valueX, valueY, valueZ);

                OnGestureDeviceParametersChanged();
            }
            else if (parameters.Length == 1) //Single Parameter handling
            {
                string nameSingleParameter = string.Empty;
                string m = GetSeperatedValue(parameters[0], out nameSingleParameter);
                int parsedSingleParameter = int.Parse(m);

                if (nameSingleParameter == "M") //means MouseButton --> MouseButton-Emulation
                {
                    MouseButtonState? mouseButtonState = null;

                    switch (parsedSingleParameter)
                    {
                        case 1:
                            mouseButtonState = MouseButtonState.LeftButtonDown;
                            break;

                        case 2:
                            mouseButtonState = MouseButtonState.LeftButtonUp;
                            break;

                        case 3:
                            mouseButtonState = MouseButtonState.RightButtonDown;
                            break;

                        case 4:
                            mouseButtonState = MouseButtonState.RightButtonUp;
                            break;

                        default:
                            throw new NotImplementedException();
                    }

                    OnMouseEmulationStateChanged(new MouseEmulationStateEventArgs(null, mouseButtonState));
                }
                else if (nameSingleParameter == "G") //means G --> Gesture Recording
                {
                    switch (parsedSingleParameter)
                    {
                        case 1:
                            OnRecordingStart();
                            break;

                        case 2:
                            OnRecordingFinish();
                            break;

                        default:
                            throw new NotImplementedException();
                    }
                }
            }
        }

        private void MotionTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (AccelerationGestureState != null)
            {
                float dSamplePeriod = 0.01F;
                float dWiiOffsetX = 1.4F;
                float dWiiOffsetY = -0.08F;
                float nSpeedGain = 0.1F;

                float dWiiMoteSetSpeedX = AccelerationGestureState.X - dWiiOffsetX;   // CURRENT position X from WiiMote

                // Using the Signed Square function:
                int nSign = Math.Sign(dWiiMoteSetSpeedX);   // Mind the Wiimote directions ...
                float dWiiMoteSSXQuadratic = (float)Math.Pow((double)dWiiMoteSetSpeedX, 2.0) * nSign;

                _currentPositionX = (_currentPositionX + (dWiiMoteSSXQuadratic * dSamplePeriod) * nSpeedGain);   // INTEGRATOR

                // Do some limitations ...
                if (_currentPositionX > 1) _currentPositionX = 1;
                if (_currentPositionX < 0) _currentPositionX = 0;

                //---------- POSITION_Y:------------------------------------------
                float dWiiMoteSetSpeedY = AccelerationGestureState.Y - dWiiOffsetY;   // CURRENT position Y from WiiMote
                //lblVy.Text = dWiiMoteSetSpeedY.ToString();  // Just for debug visualisation

                // Using the Signed Square function:
                nSign = Math.Sign(dWiiMoteSetSpeedY);   // Mind the Wiimote directions ...
                float dWiiMoteSSYQuadratic = (float)Math.Pow((double)dWiiMoteSetSpeedY, 2.0) * nSign;

                _currentPositionY = (_currentPositionY + (dWiiMoteSSYQuadratic * dSamplePeriod) * nSpeedGain);   // INTEGRATOR

                // Do some limitations ...
                if (_currentPositionY > 1) _currentPositionY = 1;
                if (_currentPositionY < 0) _currentPositionY = 0;


                PointF? mousePosition = new PointF(_currentPositionX, _currentPositionY);

                OnMouseEmulationStateChanged(
                new MouseEmulationStateEventArgs(
                    new PointF(_currentPositionX, _currentPositionY),
                    null));
            }
        }

        private void OnMouseEmulationStateChanged(MouseEmulationStateEventArgs mouseEmulationStateEventArgs)
        {
            EventHandler<MouseEmulationStateEventArgs> temp = MouseEmulationStateChanged;
            if (temp != null)
            {
                temp(this, mouseEmulationStateEventArgs);
            }
        }

        private string GetSeperatedValue(string combinedString, out string name)
        {
            string[] values = combinedString.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);

            name = values[0];

            return values[1];
        }

        #region IAccelerationGestureDevice Members

        public AccelerationGestureState AccelerationGestureState { get; private set; }

        #endregion
    }
}
